package client;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;

/**
 * Classe dedicata al collegamento con il server tramite protocollo socket, preoccupandosi di inviare e ricevere i messaggi.<br />
 * Su questi ultimi, la classe spacchetta il singolo messaggio ed elabora i dati, chiamando infine
 * i metodi dell'interfaccia grafica.<br \>
 * Questa classe e' eseguita da un thread diverso da quello che si occupa di gestire l'interfaccia grafica, poiche' rimane sempre in ascolto sui messaggi inviati dal server.
 */
public class Client_listener extends Thread{
	
	private ArrayList<Integer> codacall = new ArrayList<Integer>();
	private enum Metodo
	{
		ok, no, listaGiocatori, classifica, mappaGenerale, listaDinosauri, userPassNonValidi, nessunoInPartita,  vistaLocale, utenteHaRazza, statoDinosauro, cambioTurno, usernameOccupato, autenticazioneFallita, nomeRazzaOccupato, tokenNonValido, troppiGiocatori, nonInPartita, idNonValido, destinazioneNonValida, raggiuntoLimiteMosseDinosauro, mortePerInedia, nonIlTuoTurno, raggiuntoNumeroMaxDinosauri, razzaNonCreata, specieMorta, nomeRazzaNonValido, comandoNonValido, connessioneInterrotta
	}
	private Socket sock;
	private Client_socket as;
	private BufferedReader buffin;
	private BufferedWriter buffout;
	private C_GSessioneGioco sg;
	private int token;
	
	/**
	 * Costruttore di classe, imposta le variabili necessarie alle chiamate ai metodi che deve eseguire
	 * la classe Client_listener.
	 * @param afso Variabile utile per comunicare con il server senza passare cosi' dall'interfaccia
	 * grafica.
	 * @param gioco Variabile necessaria per poter chiamare i metodi dell'interfaccia grafica,
	 * comunicandogli i parametri ricevuti dal server.
	 */
	public Client_listener(Client_socket afso, C_GSessioneGioco gioco){
		this.as = afso;
		this.sg = gioco;
	}
	
	/**
	 * Metodo che permette di far partire il Thread inerente a questa classe. Quetsa classe ha infatti un thread dedicato poiche' resta costantemente in ascolto sulle risposte del server.
	 */
	public void run(){
		
		try {
			sock = new Socket("10.42.43.1", 34567); //indirizzo ip e porta per connettersi al server
			//sock = new Socket("localhost", 34567);
			buffin = new BufferedReader(new InputStreamReader(sock.getInputStream())); //oggetti per leggere e inviare i messaggi al/dal server
			buffout = new BufferedWriter(new OutputStreamWriter(sock.getOutputStream()));
			System.out.println("presa connessione");
			listening();
		} catch (UnknownHostException e) {
			e.printStackTrace();
		} catch (IOException e) {
			noConnection();
		}
	}
	
	/**
	 * Metodo dedicato all'invio (del messaggio passatogli) al server. Accoda inoltre in un ArrayList un intero indicante quale metodo ha chiamato al server.
	 * @param mess Stringa contenente il messaggio da inviare al server.
	 * @param x Parametro utilizzato per tenere in memoria le richieste fatte al server, come una coda.
	 */
	public void invio(String mess, int x){
		
		try{
		codacall.add(x); //aggiunta alla coda delle richieste effettuate il numero corrispondente al metodo chiamato
		buffout.write(mess);
		buffout.newLine();
		buffout.flush();
		System.out.println("Inviato messaggio: (" + mess + ")");
		} catch(NullPointerException e){
			System.out.println("Connessione non ancora stabilita");
			e.printStackTrace();
		} catch (IOException e) {
			System.out.println("Connessione non ancora stabilita");
			e.printStackTrace();
		}
	}
	
	/**
	 * Metodo che permette alla classe di rimanere costantemente in ascolto sui messaggiinviati dal server. Controlla la validita' della stringa ricevuta, in caso contrario chiama un metodo per riconettersi al server.
	 * @throws IOException Eccezione lanciata in caso di errori avvenuti in lettura dal server.
	 */
	private void listening() throws IOException{
		String mess = null;
		
		while(true){
			try{
				mess = buffin.readLine(); //lettura dei messaggi ricevuti dal server
				System.out.println("Ricevuto messaggio: (" + mess + ")");
				if( mess.length() > 0 ) //controlo sulla validita' della stringa ricevuta dal server
					spacchetta(mess);
				else spacchetta("@comandononValido");
				} catch (NullPointerException e){ //eccezione lanciata in caso di ricezione steinga non valida
					mess = "@no,@connessioneInterrotta";
					spacchetta(mess);
				}
		}
	}
	
	/**
	 * Questo metodo spacchetta e verifica la prima parte del messaggio per trovare la risposta data dal server in seguito ad una richiesta effettuata precedentemente (es. ok per una conferma di buona riuscita di un metodo precedentemente chiamato). In caso di altri dati ricevuti dal server, li prepara e chiama i metodi dell'interfaccia grafica (es. per classifica prende i dati realtivi agli utenti, li pone in un array e chiama un metodo dell'interfaccia grafica passandogli l'intero array). <br \>Infine, il metodo elimina la prima cella dell'array inerente alla coda delle chiamate effettuate lato client.
	 * @param mess Stringa rappresentante l'informazione ricevuta dal server, pronta per essere elaborata.
	 */
	private void spacchetta(String mess){
		int i = 0;
		int j = 0;
		int lung = 0;
		int countx = 0;
		int county = 0;
		int tk, x, y, rig, col;
		char c;
		String metodo, support;
		String[] lista;
		String[][] mappa;
		boolean f = false; //false stringa non finita, true stringa finita
		
		if( mess.contains(",") ){
			i = mess.indexOf(",");
			metodo = mess.substring(1, i);
			f = false; //false nel caso la stringa contenga altri parametri utili
		}
		else{
			metodo = mess.substring(1);
			f = true; //true nel caso la stringa sia solo di conferma (es. @ok)
		}
		
		switch(Metodo.valueOf(metodo)){ //la stringa metodo precedentemente inizializzata rappreseta il tipo di messaggoi ricevuto dal server
		
			case ok: //caso ok, risposta ricevuto ad esempio in seguito ad una richiesta di login, logout, etc.
				if( codacall.get(0) == 0 )
					sg.creaUtente();
				if( codacall.get(0) == 1 ){
					metodo = mess.substring(i+1);
					tk = Integer.parseInt(String.valueOf(metodo));
					token = tk;
					sg.login(tk);
				}
				if( codacall.get(0) == 2 ); //in caso di richiesta creaRazza, non devo chiamare nessun metodo dell'interfaccia grafica
				if( codacall.get(0) == 3 )
					sg.accessoPartita();
				if( codacall.get(0) == 4 )
					sg.uscitaPartita();
				if( codacall.get(0) == 5 )
					sg.logout();
				if( codacall.get(0) == 6 ){
					if( f == true){ //caso movimento senza combattimento
						sg.muoviDinosauro(""); 
					}
					else{ //caso movimento con combattimento
						i = mess.indexOf(",", i++);
						metodo = mess.substring(i+1);
						sg.muoviDinosauro(metodo);
					}
				}	
				if( codacall.get(0) == 7 )
					as.mappaGenerale(token);
				if( codacall.get(0) == 8 ){
					as.mappaGenerale(token);
				}
				if( codacall.get(0) == 9 );
				if( codacall.get(0) == 10 );
				break;
				
			case listaGiocatori:
				metodo = mess.substring(i+1);
				lista = metodo.split(","); //recupero dalla stringa i nomi dei giocatori
				sg.listaGiocatori(lista);
				break;
				
			case classifica:	
				if( f == true)
					sg.classifica(null);
				else{
					lung = mess.lastIndexOf("}");
					countx = 0;
					j = 0;
					while( j != lung ){
						j = mess.indexOf("}", j+1);
						countx++;
					}
					i = 0;
					j = 0;
					mappa = new String[countx][4];
					for( int k = 0; k < countx; k++){ //estraggo dalla stringa le informazioni relative ai giocatri e alle loro speci
						i = mess.indexOf("{", j);
						j = mess.indexOf("}", i);
						metodo = mess.substring(i+1, j);
						lista = metodo.split(",");
						mappa[k][0] = lista[0];
						mappa[k][1] = lista[1];
						mappa[k][2] = lista[2];
						mappa[k][3] = lista[3];
					}
					sg.classifica(mappa);
				}
				break;
				
			case mappaGenerale:
				j = mess.indexOf(",", i+1);
				x = Integer.parseInt(String.valueOf(mess.substring(i+2, j)));
				i = mess.indexOf("}", j+1);
				y = Integer.parseInt(String.valueOf(mess.substring(j+1, i)));
				metodo = mess.substring(i+2);
				mappa = new String[x][y];
				i = 0;
				countx = 0;
				county = 0;
				lung = metodo.length();
				while( lung != 0 ){ //preparo la stringa da passare all'interfaccia grafica
					c = (metodo.charAt(i));
					if(c == '[' || c == ']' || c == ';'){
						if( c == ';' ){
							countx++;
							county=0;
						}	
						i++;
						lung--;
					}
					else{
						mappa[countx][county] = String.valueOf(c);
						county++;
						i++;
						lung--;
					}	
				}
				sg.mappaGenerale(x, y, mappa);
				break;
				
			case listaDinosauri: 
				metodo = mess.substring(i+1);
				lista = metodo.split(","); //estraggo dalla lista i nomi dei dinosauri
				sg.listaDinosauri(lista);
				break;
				
			case vistaLocale:
				j = mess.indexOf(",", i+1);
				x = Integer.parseInt(String.valueOf(mess.substring(i+2, j)));
				i = mess.indexOf("}", j+1);
				y = Integer.parseInt(String.valueOf(mess.substring(j+1, i)));
				j = mess.indexOf(",", i+2);
				rig = Integer.parseInt(String.valueOf(mess.substring(i+3, j)));
				i = mess.indexOf("}", j);
				col = Integer.parseInt(String.valueOf(mess.substring(j+1, i)));
				metodo = mess.substring(i+2);
				mappa = new String[rig][col];
				i = 0;
				countx = 0;
				county = 0;
				lung = metodo.length();
				while( lung != 0 ){ //estraggo dalla stringa le informazioni sulla vista locale che saranno poi passate all'nterfaccia grafica
					c = (metodo.charAt(i));
					if(c == '[' || c == ']' || c == ';'){
						if( c == ';' ){
							countx++;
							county=0;
						}	
						i++;
						lung--;
					}
					else{
						if( metodo.charAt(i+1) == ','){
							j = metodo.indexOf("]", i);
							support = metodo.substring(i, j);
							lung = lung - support.length();
							mappa[countx][county] = support;
							i = j-1;
						}
							
						else {
							mappa[countx][county] = String.valueOf(c);
							lung--;
						}
						county++;
						i++;
						
					}	
				}
				sg.vistaLocale(x, y, rig, col, mappa);
				break;
				
			case statoDinosauro:
				metodo = mess.substring(i+1);
				lista = metodo.split(",");
				lung = lista.length;
				for ( j = 0; j < lung; j++ ){ //estrapolo le informazioni riguardo al singolo dinosauro
					if( lista[j].contains("}") ){
						lista[j] = lista[j].replace("}", "");
					}
					if( lista[j].contains("{") )
						lista[j] = lista[j].replace("{", "");
				}
				c = lista[2].charAt(0);
				x = Integer.parseInt(lista[3]);
				y = Integer.parseInt(lista[4]);
				countx = Integer.parseInt(lista[5]);
				if( lista.length == 6)
					sg.statoDinosauro(lista[0], lista[1], c, x, y, Integer.parseInt(lista[5]), -1, -1);
				else sg.statoDinosauro(lista[0], lista[1], c, x, y, Integer.parseInt(lista[5]), Integer.parseInt(lista[6]), Integer.parseInt(lista[7]));
				break;
				
			case cambioTurno:
				metodo = mess.substring(i+1);
				sg.cambioTurno(metodo);
				codacall.add(0, 666); //accordo una richiesta fittizia, cioe' non accodata direttamente dal client
				break;
				
			case specieMorta:
				metodo = mess.substring(i+1);
				sg.specieMorta(metodo);
				if ( codacall.size() == 0 )
					codacall.add(0, 667); //accordo una richiesta fittizia, cioe' non accodata direttamente dal client
				break;
			
			case no:
				if(f == true){
					//errore generico
				}
				else{
					i = mess.indexOf("@", i);
					metodo = mess.substring(i+1);
					switch(Metodo.valueOf(metodo)){ //switch di errori
					
					case usernameOccupato:
						sg.errore(0);
						break;
						
					case autenticazioneFallita:
						sg.errore(1);
						break;
						
					case nomeRazzaOccupato:
						sg.errore(2);
						break;
						
					case tokenNonValido:
						sg.errore(3);
						break;
						
					case troppiGiocatori:
						sg.errore(4);
						break;
						
					case nonInPartita:
						sg.errore(5);
						break;
						
					case idNonValido:
						sg.errore(6);
						break;
						
					case destinazioneNonValida:
						sg.errore(7);
						break;
						
					case raggiuntoLimiteMosseDinosauro:
						sg.errore(8);
						break;
						
					case mortePerInedia:
						sg.errore(9);
						break;
						
					case nonIlTuoTurno:
						sg.errore(10);
						break;
						
					case raggiuntoNumeroMaxDinosauri:
						sg.errore(11);
						break;
						
					case nessunoInPartita:
						sg.errore(95);
						break;
						
					case razzaNonCreata:
						sg.errore(96);
						break;
						
					case utenteHaRazza:
						sg.errore(97);
						break;
						
					case nomeRazzaNonValido:
						sg.errore(98);
						break;
						
					case userPassNonValidi:
						sg.errore(99);
						break;
						
					case comandoNonValido:
						break;
						
					case connessioneInterrotta:
						noConnection();
						break;
					}
				}
				break;
		}
		try {
			codacall.remove(0); //rimozione dalla coda della richiesta fatta all'inizio dal client
		} catch (IndexOutOfBoundsException e){}
	}

	/**
	 * Metodo invocato nel caso la connessione con il server abbia riscontrato dei problemi.<br \>
	 * Il client viene rimandato alla finestra di avvio con relativa scelta connessione.
	 */
	@SuppressWarnings("deprecation")
	private void noConnection(){
		/*try {
			System.out.println("Errore connessione: tentativo di connessione tra 15 secondi");
			Thread.sleep(15000); //si attende 15 secondo prima del nuovo tentativo di connessione
			sg.uscitaPartita();
			sg.logout();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		this.run();*/
		sg.erroreConnessione();
		super.stop();
		this.stop();
	}
}
